/**
 * 
 */
package cn.cityhouse.avm.utils;

import org.apache.commons.math3.exception.NullArgumentException;
import org.apache.commons.math3.linear.Array2DRowRealMatrix;
import org.apache.commons.math3.linear.ArrayRealVector;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.linear.RealVector;

/**
 * @author <a href="mailto:wangcl@lreis.ac.cn">ChenLiang Wang</a>
 *
 */
public class MatrixUtil2 {

	/**
	 * Convenience method which will return a boolean which indicates whether or
	 * not the dimensions of the two matrices are equal. i.e., the method will
	 * return true if the number of rows in matrixA is equal to the number of
	 * rows in matrixB, and the number of columns in matrixA equals the number
	 * of columns in matrixB.
	 * 
	 * @param matrixA
	 *            first matrix to be compared
	 * @param matrixB
	 *            second matrix to be compared
	 * @return boolean indicating whether or not the matrix dimensions are
	 *         equal.
	 */
	public static boolean areDimensionsEqual(RealMatrix matrixA, RealMatrix matrixB) {
		int numRowsA = matrixA.getRowDimension();
		int numColsA = matrixA.getColumnDimension();
		int numRowsB = matrixB.getRowDimension();
		int numColsB = matrixB.getColumnDimension();
		return (numRowsA == numRowsB && numColsA == numColsB);
	}

	/**
	 * Generates column-wise sum for the input matrix
	 * 
	 * @param rlm
	 *            the input matrix
	 * @return column-wise sum
	 * @throws NullArgumentException
	 *             null input matrix
	 */
	public static RealVector getColSum(RealMatrix rlm) {
		if (rlm == null) {
			throw new NullArgumentException();
		}
		int ncol_rlm = rlm.getColumnDimension();
		RealVector rlv_colsum = new ArrayRealVector(ncol_rlm);
		for (int i = 0; i < ncol_rlm; i++) {
			rlv_colsum.setEntry(i, getSum(rlm.getColumnVector(i)));
		}
		return rlv_colsum;
	}

	/**
	 * Creates a matrix of equal dimension but with all non-diagonal elements
	 * set to 0
	 * 
	 * @param matrix
	 *            the matrix from which to extract the diagonal
	 * @return diagonal matrix
	 * @throws IllegalArgumentException
	 *             null input matrix
	 */
	public static RealMatrix getDiagonalMatrix(RealMatrix matrix) throws IllegalArgumentException {
		if (matrix == null)
			throw new IllegalArgumentException("null input matrix");

		double[][] zData = new double[matrix.getRowDimension()][matrix.getColumnDimension()];
		for (int row = 0; row < matrix.getRowDimension(); row++) {
			for (int col = 0; col < matrix.getColumnDimension(); col++) {
				if (row == col)
					zData[row][col] = matrix.getEntry(row, col);
				else
					zData[row][col] = 0;
			}
		}
		return new Array2DRowRealMatrix(zData);
	}

	/**
	 * Creates a vector from the diag elements from input matrix
	 * 
	 * @param rlm
	 *            the input matrix
	 * @return the diagonal element vector
	 * @throws IllegalArgumentException
	 *             null input matrix
	 */
	public static RealVector getDiagVector(RealMatrix rlm) throws IllegalArgumentException {
		if (rlm == null)
			throw new IllegalArgumentException("null input matrix");
		int size = Math.min(rlm.getColumnDimension(), rlm.getRowDimension());
		RealVector rlv = new ArrayRealVector(size);
		for (int ii = 0; ii < size; ii++) {
			for (int jj = 0; jj < size; jj++) {
				if (ii == jj) {
					rlv.setEntry(ii, rlm.getEntry(ii, jj));
				}
			}
		}
		return rlv;
	}

	/**
	 * This method will return the element-wise product of matrixA and matrixB.
	 * In order to perform element-wise multiplication, the incoming matrices
	 * must have the same dimensions.
	 * 
	 * @param matrixA
	 *            the first matrix
	 * @param matrixB
	 *            the second matrix
	 * @return RealMatrix which is the element-wise product of A and B.
	 * @throws IllegalArgumentException
	 *             Both matrices must be non-null,and matrix dimensions must be
	 *             equal
	 */
	public static RealMatrix getElementWiseProduct(RealMatrix matrixA, RealMatrix matrixB)
			throws IllegalArgumentException {
		if (matrixA == null || matrixB == null || !areDimensionsEqual(matrixA, matrixB)) {
			throw new IllegalArgumentException("Both matrices must be non-null " + "and matrix dimensions must be equal"
					+ " for element-wise multiplication.");
		}

		int numRows = matrixA.getRowDimension();
		int numCols = matrixA.getColumnDimension();
		RealMatrix product = new Array2DRowRealMatrix(numRows, numCols);
		double aVal, bVal;

		// loop through each row
		for (int r = 0; r < numRows; r++) {
			// multiply each element of A by same element of B
			for (int c = 0; c < numCols; c++) {
				aVal = matrixA.getEntry(r, c);
				bVal = matrixB.getEntry(r, c);
				product.setEntry(r, c, aVal * bVal);
			}
		}
		return product;
	}

	/**
	 * Find the maximum value in the vector
	 * 
	 * @param rlv
	 *            the input vector
	 * @return the max value of the elements of the vector
	 */
	public static double getMaxValue(RealVector rlv) {
		double max;
		if (rlv == null) {
			max = Double.NaN;
		} else {
			max = Double.NEGATIVE_INFINITY;
			for (int r = 0; r < rlv.getDimension(); r++) {
				double value = rlv.getEntry(r);
				if (value > max) {
					max = value;
				}
			}
		}
		return max;
	}

	/**
	 * Find the maximum value in the matrix
	 * 
	 * @param matrix
	 *            the input maxtrix
	 * @return the max value of the elements of the matrix
	 */
	public static double getMaxValue(RealMatrix matrix) {
		double max;
		if (matrix == null) {
			max = Double.NaN;
		} else {
			max = Double.NEGATIVE_INFINITY;
			for (int r = 0; r < matrix.getRowDimension(); r++) {
				for (int c = 0; c < matrix.getColumnDimension(); c++) {
					double value = matrix.getEntry(r, c);
					if (value > max) {
						max = value;
					}
				}
			}
		}
		return max;
	}

	/**
	 * Creates a matrix of the specified size with all cells set to the
	 * specified value
	 * 
	 * @param rows
	 *            row dimension
	 * @param cols
	 *            column dimension
	 * @param value
	 *            fill value
	 * @return rows x cols matrix with all cells set to the specified value
	 */
	public static RealMatrix getRealMatrixWithFilledValue(int rows, int cols, double value) {
		double[][] data = new double[rows][cols];

		for (int r = 0; r < rows; r++) {
			for (int c = 0; c < cols; c++) {
				data[r][c] = value;
			}
		}
		return new Array2DRowRealMatrix(data);
	}

	/**
	 * Generate reciprocal vector for the input vector
	 * 
	 * @param rlv
	 *            the input vector
	 * @return RealVector in which elements are the reciprocal for the rlv
	 * @throws IllegalArgumentException
	 *             Null vector
	 */
	public static RealVector getReciprocal(RealVector rlv) {
		if (rlv == null) {
			throw new IllegalArgumentException("Null vector not allowed for " + "getReciprocal()");
		}
		RealVector rlv_s = rlv.copy();
		for (int r = 0; r < rlv.getDimension(); r++) {
			rlv_s.setEntry(r, 1.0 / rlv.getEntry(r));
		}
		return rlv_s;
	}

	/**
	 * Generate reciprocal vector multiplied by num for the input vector
	 * 
	 * @param rlv
	 *            the input vector
	 * @param num
	 *            the multiplied number
	 * @return the reciprocal vector multiplied by num
	 * @throws IllegalArgumentException
	 *             null input rlv
	 */
	public static RealVector getReciprocal(RealVector rlv, double num) {
		if (rlv == null) {
			throw new IllegalArgumentException("Null matrix not allowed for " + "getSumOfSquares()");
		}
		RealVector rlv_s = rlv.copy();
		for (int r = 0; r < rlv.getDimension(); r++) {
			rlv_s.setEntry(r, num / rlv.getEntry(r));
		}
		return rlv_s;
	}

	/**
	 * Return the sum of the vector
	 * 
	 * @param rlv
	 *            the input vector
	 * @return the sum of rlv
	 */
	public static double getSum(RealVector rlv) {
		if (rlv == null) {
			throw new IllegalArgumentException("Null matrix not allowed for " + "getSumOfSquares()");
		}
		double sum = 0.0;
		for (int r = 0; r < rlv.getDimension(); r++) {
			double value = rlv.getEntry(r);
			sum += value;

		}
		return sum;
	}

	/**
	 * Return the element wise sum of squares
	 * 
	 * @param matrix
	 *            input matrix
	 * @return sum of squares
	 */
	public static double getSumOfSquares(RealMatrix matrix) {
		if (matrix == null) {
			throw new IllegalArgumentException("Null matrix not allowed for " + "getSumOfSquares()");
		}
		double sum = 0.0;
		for (int r = 0; r < matrix.getRowDimension(); r++) {
			for (int c = 0; c < matrix.getColumnDimension(); c++) {
				double value = matrix.getEntry(r, c);
				sum += value * value;
			}
		}
		return sum;
	}

	/**
	 * Return sum of squares for input vector
	 * 
	 * @param rlv
	 *            input vector
	 * @return sum of squares
	 */
	public static double getSumOfSquares(RealVector rlv) {
		if (rlv == null) {
			throw new IllegalArgumentException("Null matrix not allowed for " + "getSumOfSquares()");
		}
		double sum = 0.0;
		for (int r = 0; r < rlv.getDimension(); r++) {
			double value = rlv.getEntry(r);
			sum += value * value;

		}
		return sum;
	}

	/**
	 * Return the product for input matrix and
	 * 
	 * @param rlm
	 *            input matrix
	 * @param rlv
	 *            input vector
	 * @return the
	 */
	public static RealMatrix productVectorByCol(RealMatrix rlm, RealVector rlv) {
		RealMatrix rlm_mat = rlm.copy();
		for (int i = 0; i < rlm.getColumnDimension(); i++) {
			rlm_mat.setColumnVector(i, rlm_mat.getColumnVector(i).ebeMultiply(rlv));
		}
		return rlm_mat;
	}

	/**
	 * Return the vector of replicate Elements of Vectors
	 * 
	 * @param rlv
	 *            input vectors
	 * @param each
	 *            non-negative integer. Each element of x is repeated each
	 *            times.
	 * @return the vector combining the replicate elements of rlv
	 */
	public static RealVector repeat(RealVector rlv, int each) {
		if (each <= 0) {
			throw new IllegalArgumentException("each must be non-negative integer");
		}
		if (rlv == null) {
			throw new IllegalArgumentException("Null Vector not allows for reapeat()");
		}
		RealVector rlv_rep = new ArrayRealVector(rlv.getDimension() * each);
		int ii = 0;
		for (int i = 0; i < rlv.getDimension(); i++) {
			for (int j = 0; j < each; j++) {
				rlv_rep.setEntry(i * each + j, rlv.getEntry(i));
			}
		}
		return rlv_rep;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @deprecated
	 * @param rlm_x
	 *            input matrix
	 * @param dim
	 *            int indicating whether row or column is to be compute
	 * @param rlv_stat
	 *            to be sweed
	 * @return sweep RealMatrix
	 */
	@Deprecated
	public static RealMatrix sweep(RealMatrix rlm_x, int dim, RealVector rlv_stat) {
		throw new UnsupportedOperationException("not implemented yet");
	}

	private MatrixUtil2() {
		// TODO Auto-generated constructor stub
		throw new Error("Do not instantiating this Class");
	}

}
